package fr.lelouet.monitoring.snapshot;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.lelouet.monitoring.impl.PlugwisedHypervisor;
import fr.lelouet.tools.Configurable;

public class HVSnapshot extends HashMap<String, VMSnapshot> implements
		Serializable, Configurable {

	private static final long serialVersionUID = 1L;

	private static final Logger logger = LoggerFactory
			.getLogger(HVSnapshot.class);

	/**
	 * the key to specify in configuration how many ms a VM should be kept in
	 * memory when no information has been received. ie, the timeout to set the
	 * vm to "destroyed"
	 */
	public static final String VMAGE_KEY = "snapshot.vmage_ms";

	public static final Long VM_AGE_MS_DEFAULT = 60000L;

	/** the number of ms a wm is kept in memory while not being refreshed */
	protected long vmage_ms = VM_AGE_MS_DEFAULT;

	public void setVMAge(long ms) {
		vmage_ms = ms;
	}

	@Override
	public void configure(Properties prop) {
		try {
			setVMAge(Long.parseLong(prop.getProperty(VMAGE_KEY,
					VM_AGE_MS_DEFAULT.toString())));
		} catch (Exception e) {
			logger.warn(e.toString());
		}
	}

	/** last time this was updated */
	public long lastUpdate = 0;

	/** consumption, in W, of the hypervisor */
	public float consumption = 0;

	/** identification of the Hypervisor in a system. */
	protected String id;

	public String getId() {
		return id;
	}

	public HVSnapshot(String id) {
		this.id = id;
	}

	@Override
	public String toString() {
		return id + "(" + lastUpdate + ", consumption=" + consumption + ")-"
				+ super.toString();
	}

	/**
	 * update this with data. The data is tested if it is VM data or hypervisor
	 * data. If it is VM data, it is delegated to the correct {@link VMSnapshot}
	 * , with a possible creation if needed
	 */
	public VMSnapshot update(Map<String, String> map) {
		try {
			if (map.containsKey("pid")) {
				String key = "pid:" + map.get("pid");
				VMSnapshot snap = get(key);
				if (snap == null) {
					snap = new VMSnapshot(key);
					put(key, snap);
				}
				snap.update(map);
				return snap;
			}
			if (map.containsKey(fr.lelouet.monitoring.perf.Tools.TIME_KEY)) {
				long time = Long.parseLong(map
						.get(fr.lelouet.monitoring.perf.Tools.TIME_KEY));
				if (time < lastUpdate) {
					return null;
				} else {
					lastUpdate = time;
				}
			} else {
				lastUpdate = System.currentTimeMillis();
			}
			if (map.containsKey(PlugwisedHypervisor.CONSUMPTION_NAME)) {
				float consumption = Float.parseFloat(map
						.get(PlugwisedHypervisor.CONSUMPTION_NAME));
				this.consumption = consumption < 0 ? 0 : consumption;
			}
			removeOldVMs();

		} catch (Exception e) {
			String message = e.getMessage() + " ; ";
			for (StackTraceElement ste : e.getStackTrace()) {
				message += ste.toString() + ";";
			}
			logger.error(message);
		}
		return null;
	}

	public void removeOldVMs() {
		long minupdate = lastUpdate - vmage_ms;
		List<String> idToRemove = new ArrayList<String>();
		for (String id : keySet()) {
			VMSnapshot vm = get(id);
			if (vm.lastUpdate < minupdate) {
				idToRemove.add(id);
			}
		}
		for (String id : idToRemove) {
			logger.info("vm {} is out of date, removing it", id);
			remove(id);
		}
	}

	public HVSnapshot snapshot() {
		return clone();
	}

	@Override
	public HVSnapshot clone() {
		HVSnapshot ret = new HVSnapshot(id);
		ret.consumption = consumption;
		ret.lastUpdate = lastUpdate;
		for (String key : keySet()) {
			ret.put(key, get(key).clone());
		}
		return ret;
	}

	/**
	 * sums the events on each vms, and add the energy
	 * 
	 * @return a map of the sum of each events of the snapshot
	 */
	public Map<String, Double> unfold() {
		HashMap<String, Double> ret = new HashMap<String, Double>();
		for (VMSnapshot vm : values()) {
			for (java.util.Map.Entry<String, Float> e : vm.entrySet()) {
				String key = e.getKey();
				Double oldval = ret.get(key);
				ret.put(key, (oldval == null ? 0 : oldval) + e.getValue());
			}
		}
		return ret;

	}

}